﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.IO;
using WeifenLuo.WinFormsUI.Docking;
using System.Drawing.Imaging;

namespace GraphicsEditor
{
    public partial class ImageForm : DockContent, IMessageFilter
    {
        public string filePathAndName;
        public Stack<Undo> undoOptions;
        public Undo currentAction; // only used when redo
        public Stack<Undo> redoOptions;
        public Point pixelCurrentLocation;
        public Point mouseImageLocation;

        private int normalImageHeight;
        public int NormalImageHeight
        {
            get { return normalImageHeight; }
            set { normalImageHeight = value; }
        }
        private int normalImageWidth;
        public int NormalImageWidth
        {
            get { return normalImageWidth; }
            set { normalImageWidth = value; }
        }

        public double currentZoom;
        public bool mouseIsDown, justZoomed;

        //public SelectTool selectTool;
        //public DrawLine drawLine;

        public ImageForm(string filename)
        {

            InitializeComponent();

            Application.AddMessageFilter(this);

            imageFormBindingSource.Add(this);
            DoubleBuffered = true;
            mouseIsDown = false;
            undoOptions = new Stack<Undo>();
            redoOptions = new Stack<Undo>();
            panelImage.AutoScroll = true;
            justZoomed = false;
            //selectTool = new SelectTool();
            //drawLine = new DrawLine();
            this.Text = Path.GetFileName(filename);
            // pw.Image = Filters.invert(blankBitmap);               //added this for rotate.
            currentZoom = 100;

            inToolStripMenuItem.ShortcutKeyDisplayString = "Ctrl++";
            outToolStripMenuItem.ShortcutKeyDisplayString = "Ctrl+-";

            filePathAndName = filename;
            if (filename != "newImage")     //New images are called "newImage" and should not load from a file.
                loadImage(filename);
            else
                newImage();
            menuStrip1.Hide();
            focusButton.Focus();
            focusButton.SendToBack();
            //ConvolutionMatrix.addMenuItems(convolutionToolStripMenuItem); //proof of concept
        }

        private void newImage()
        {
            Bitmap bmp = new Bitmap(100, 100, PixelFormat.Format32bppArgb);
            pictureWindow.Image = bmp;
            pictureWindow.Width = bmp.Width;
            pictureWindow.Height = bmp.Height;
            normalImageHeight = bmp.Height;
            normalImageWidth = bmp.Width;
        }

        private void loadImage(string imageSource)
        {
            try
            {
                if (imageSource != null && imageSource != "")
                {
                    //File name is good open MDI window as child

                    FileStream openStream = new FileStream(imageSource, FileMode.Open);
                    Image tempImage = Image.FromStream(openStream);
                    Bitmap bmp = new Bitmap(tempImage);
                    pictureWindow.Image = bmp;
                    pictureWindow.Width = bmp.Width;
                    pictureWindow.Height = bmp.Height;
                    normalImageHeight = bmp.Height;
                    normalImageWidth = bmp.Width;
                    openStream.Close();
                    Console.Write(pictureWindow.Image.RawFormat.ToString());
                }

            }
            catch (Exception ex)
            {
                MessageBox.Show("Error: Could not read file from disk. Original error: " + ex.Message);
            }
        }

        private void saveToolStripMenuItem_Click(object sender, EventArgs e)
        {
            try
            {
                if (filePathAndName != "" && filePathAndName != null)
                    // add some error codes or do some stuff before saving here
                    pictureWindow.Image.Save(filePathAndName);
                else
                    saveToolStripMenuItem_Click(sender, e);
            }
            catch (Exception ex)
            {
                MessageBox.Show("Error: Could not save file to disk. Original error: " + ex.Message);
            }
        }

        private void saveAsToolStripMenuItem_Click(object sender, EventArgs e)
        {
            SaveFileDialog saveFile = new SaveFileDialog();
            //This should probably be narrowed down/changed as options/files are added, this is just an example for now
            saveFile.Filter = "Image Files(*.BMP;*.JPG;*.GIF;*.PNG;*.TIFF)|*.BMP;*.JPG;*.GIF;*.PNG;*.TIFF|All files (*.*)|*.*";
            if (saveFile.ShowDialog() == DialogResult.OK)
            {
                try
                {
                    if (saveFile.FileName != null && saveFile.FileName != "")
                    {
                        // add some error codes or do some stuff before saving here
                        pictureWindow.Image.Save(saveFile.FileName);
                    }
                }
                catch (Exception ex)
                {
                    MessageBox.Show("Error: Could not save file to disk. Original error: " + ex.Message);
                }
            }
        }

        private void closeToolStripMenuItem_Click(object sender, EventArgs e)
        {
            this.Close();
        }

        private void sobelToolStripMenuItem_Click(object sender, EventArgs e)
        {
            BitmapTask();
            Bitmap currentImage = new Bitmap(pictureWindow.Image);
            pictureWindow.Image = ConvolutionMatrix.sobel(currentImage);
        }

        private void smoothToolStripMenuItem_Click(object sender, EventArgs e)
        {
            BitmapTask();
            Bitmap currentImage = new Bitmap(pictureWindow.Image);
            pictureWindow.Image = ConvolutionMatrix.smooth(currentImage, 1);
        }

        private void grayscaleToolStripMenuItem_Click(object sender, EventArgs e)
        {
            BitmapTask();
            Bitmap currentImage = new Bitmap(pictureWindow.Image);
            pictureWindow.Image = Filters.grayScale(currentImage);
        }

        private void invertToolStripMenuItem_Click(object sender, EventArgs e)
        {
            BitmapTask();
            // Maybe save different bitmaps in memory using List or something for an undo option
            Bitmap currentImage = new Bitmap(pictureWindow.Image);
            pictureWindow.Image = Filters.invert(currentImage);
        }

        private void winterizeToolStripMenuItem_Click(object sender, EventArgs e)
        {
            BitmapTask();
            // Maybe save different bitmaps in memory using List or something for an undo option
            Bitmap currentImage = new Bitmap(pictureWindow.Image);
            pictureWindow.Image = Filters.winterize(currentImage);
        }

        private void editToolStripMenuItem_DropDownOpening(object sender, EventArgs e)
        {
            if (undoOptions.Count == 0)
                undoToolStripMenuItem.Enabled = false;
            else
                undoToolStripMenuItem.Enabled = true;

            if (redoOptions.Count == 0)
                redoToolStripMenuItem.Enabled = false;
            else
                redoToolStripMenuItem.Enabled = true;
        }

        private void undoToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (undoOptions.Count != 0)
            {
                Undo item = undoOptions.Pop();
                //store the popped item in current action
                //this allows us to know what needs done for redo
                currentAction = item;
                //there is something to undo
                //check the item, if it is a bitmap item to a standard bitmap swap
                if (item.bitmap != null)
                {
                    //standard bitmap swap, push current bitmap to redo stack
                    Undo temp = new Undo(new Bitmap(pictureWindow.Image));
                    redoOptions.Push(temp);
                    pictureWindow.Image = item.bitmap;
                }
                else if (item.isMoveOrSize)
                {
                    //select box has been moved as last action
                    //store current selection in undo object and push onto redo stack
                    Point start = ToolBox.selectTool.getStartPoint();
                    Point drop = ToolBox.selectTool.getDropPoint();
                    Undo temp = new Undo(start, drop);
                    redoOptions.Push(temp);
                    //perform undo move


                }
                else if (item.isPaste)
                {
                    //just pasted
                    redoOptions.Push(new Undo());
                    //perform undo paste


                }
                //if the item is something else perform that action.

                redoToolStripMenuItem.Enabled = true;
                if (undoOptions.Count == 0)
                    undoToolStripMenuItem.Enabled = false;
            }
            else
            {
                undoToolStripMenuItem.Enabled = false;
            }
        }

        private void redoToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (redoOptions.Count != 0)
            {
                Undo item = redoOptions.Pop();

                if (currentAction.bitmap != null)
                {
                    Undo temp = new Undo(new Bitmap(pictureWindow.Image));
                    undoOptions.Push(temp);
                    pictureWindow.Image = item.bitmap;
                }
                else if (currentAction.isMoveOrSize)
                {
                    Point start = ToolBox.selectTool.getStartPoint();
                    Point drop = ToolBox.selectTool.getDropPoint();
                    Undo temp = new Undo(start, drop);
                    
                }
                else if (currentAction.isPaste)
                {

                }
             //   undoOptions.Push(new Bitmap(pictureWindow.Image));
                undoToolStripMenuItem.Enabled = true;
               // pictureWindow.Image = redoOptions.Pop();
                if (redoOptions.Count == 0)
                    redoToolStripMenuItem.Enabled = false;
            }
            else
            {
                redoToolStripMenuItem.Enabled = false;
            }
        }

        private void zoomBar_Scroll(object sender, EventArgs e)
        {

            TrackBar bar = (TrackBar)sender;
            zoom(100 / (float)bar.Value);
        }

        private void inToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (zoomBar.Value <= (zoomBar.Minimum + 19))
                zoomBar.Value = zoomBar.Minimum;
            else
                zoomBar.Value -= 20;

            zoom(100 / (float)zoomBar.Value);
        }

        private void outToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (zoomBar.Value >= (zoomBar.Maximum - 19))
                zoomBar.Value = zoomBar.Maximum;
            else
                zoomBar.Value += 20;

            zoom(100 / (float)zoomBar.Value);

        }

        private void zoom(float percentageToChange)
        {
            pictureWindow.Width = (int)Math.Floor((normalImageWidth * percentageToChange));
            pictureWindow.Height = (int)Math.Floor((normalImageHeight * percentageToChange));
            currentZoom = normalImageWidth / (float)pictureWindow.Width;
            Console.WriteLine((pictureWindow.Width / normalImageWidth).ToString());
            if (ToolBox.currentTool != SelectedTool.none)
            {
                justZoomed = true;
                pictureWindow.Invalidate();
            }
        }

        private void customToolStripMenuItem_Click(object sender, EventArgs e)
        {

        }
        private void pictureWindow_MouseMove(object sender, MouseEventArgs e)
        {
            if (pictureWindow.Image != null)
                pixelCurrentLocation = getPositionUsingMouse(e.Location);
            lblImageCoordinate.Text = "X: " + pixelCurrentLocation.X.ToString() + "Y: " + pixelCurrentLocation.Y.ToString();

            ToolBox.drawTool.mouseMoved(e.Location, pictureWindow, this, mouseIsDown);

            /*if (ToolBox.currentTool == SelectedTool.selectTool)
            {
                selectTool.mouseMoved(e.Location, pictureWindow, this); //send the cursor so we can change how it looks
            }

            if (mouseIsDown)
            {

                if (ToolBox.currentTool == SelectedTool.drawLine)
                {
                    ToolBox.drawTool.mouseMoved(e.Location, pictureWindow);
                    //drawLine.mouseMoved(e.Location, pictureWindow);
                }
            }*/

        }

        private Point getPositionUsingMouse(Point mousePoint)
        {

            //since 0,0 is always in the upper left-hand corner, its just a matter of finding
            //these ratios.
            float widthRatio = (float)pictureWindow.Width / pictureWindow.Image.Width;
            float heightRatio = (float)pictureWindow.Height / pictureWindow.Image.Height;

            Point point = new Point();
            point.X = (int)Math.Floor(mousePoint.X / widthRatio);
            point.Y = (int)Math.Floor(mousePoint.Y / heightRatio);

            return point;
        }
        private void pictureWindow_MouseDown(object sender, MouseEventArgs e)
        {
            mouseIsDown = true;
            ToolBox.drawTool.dirty = true;
            ToolBox.drawTool.setMouseDown(e.Location, pictureWindow);

            /*
            mouseIsDown = true;
            if (ToolBox.currentTool == SelectedTool.selectTool)
            {
                selectTool.setMouseDown(e.Location, pictureWindow);
            }
            else if (ToolBox.currentTool == SelectedTool.drawLine)
            {
                undoOptions.Push(new Bitmap(pictureWindow.Image));
                undoToolStripMenuItem.Enabled = true;
                ToolBox.drawTool.dirty = true;
                ToolBox.drawTool.setMouseDown(e.Location, pictureWindow);
            }
             */
        }

        private void pictureWindow_MouseUp(object sender, MouseEventArgs e)
        {
            mouseIsDown = false;

            ToolBox.drawTool.setMouseUp(e.Location, pictureWindow);
            /*
            if (ToolBox.currentTool == SelectedTool.selectTool)
            {
                selectTool.setMouseUp(e.Location, pictureWindow);
            }
            else if (ToolBox.currentTool == SelectedTool.drawLine)
            {
                ToolBox.drawTool.setMouseUp(e.Location, pictureWindow);
            }
            */
        }

        private void pictureWindow_Paint(object sender, PaintEventArgs e)
        {
            ToolBox.drawTool.paintSelection(e.Graphics, pictureWindow, justZoomed);
            ToolBox.drawTool.dirty = false;
            justZoomed = false;
            normalImageWidth = pictureWindow.Image.Width;
            normalImageHeight = pictureWindow.Image.Height;
            imageFormBindingSource.ResetCurrentItem();

            /*
            if (ToolBox.currentTool == SelectedTool.drawLine)
            {
                ToolBox.drawTool.paintSelection(e.Graphics, pictureWindow, justZoomed);
                justZoomed = false;
                ToolBox.drawTool.dirty = false;
            }
            if (selectTool.isSelected())
            {
                selectTool.paintSelection(e.Graphics, pictureWindow, justZoomed);
                justZoomed = false;
            }*/
        }

        private void pictureWindow_MouseWheel(object sender, MouseEventArgs e)
        {
            if (Control.ModifierKeys == Keys.Control)
            {


                //base.OnMouseWheel(e);
                Console.WriteLine("Control pressed while mouse wheel");

                ((HandledMouseEventArgs)e).Handled = true;

            }
        }

        void zoomBar_MouseWheel(object sender, System.Windows.Forms.MouseEventArgs e)
        {
            focusButton.Focus();
            //Console.WriteLine("Wheel");
        }

        private void pictureWindow_Click(object sender, EventArgs e)
        {
            focusButton.Focus();
        }

        private void ImageForm_Activated(object sender, EventArgs e)
        {
            Console.WriteLine("Form activated, file : " + sender.ToString());
            //use this to modify the toolbox to represent the current selected tool for this form.
        }

        private void rotateToolStripMenuItem_Click(object sender, EventArgs e)
        {
            RotateForm rotateForm = new RotateForm(this);
            rotateForm.Show();
        }

        public void BitmapTask()                                     //used with Rotate function.
        {
            Undo temp = new Undo(new Bitmap(pictureWindow.Image));
            undoOptions.Push(temp);
            undoToolStripMenuItem.Enabled = true;
            redoOptions.Clear();
            redoToolStripMenuItem.Enabled = false;
        }

        public Bitmap GetBitmap()
        {
            return new Bitmap(pictureWindow.Image);
        }
        public void SetBitmap(Bitmap bmp)
        {
            normalImageHeight = bmp.Height;
            normalImageWidth = bmp.Width;
            pictureWindow.Width = bmp.Width;
            pictureWindow.Height = bmp.Height;
            pictureWindow.Image = bmp;
            zoom(100 / (float)zoomBar.Value);
        }

        private void histogramToolStripMenuItem_Click(object sender, EventArgs e)
        {
            Other.HistogramForm histogram = new Other.HistogramForm(pictureWindow.Image);
            histogram.Show();
        }

        private void blurToolStripMenuItem_Click(object sender, EventArgs e)
        {
            BitmapTask();
            Bitmap currentImage = new Bitmap(pictureWindow.Image);
            pictureWindow.Image = ConvolutionMatrix.blur(currentImage);

        }

        private void sharpenToolStripMenuItem_Click(object sender, EventArgs e)
        {
            BitmapTask();
            Bitmap currentImage = new Bitmap(pictureWindow.Image);
            pictureWindow.Image = ConvolutionMatrix.sharpen(currentImage);

        }

        private void edgeDetectToolStripMenuItem_Click(object sender, EventArgs e)
        {
            BitmapTask();
            Bitmap currentImage = new Bitmap(pictureWindow.Image);
            pictureWindow.Image = ConvolutionMatrix.edge_detect(currentImage);

        }

        private void edgeEnhanceToolStripMenuItem_Click(object sender, EventArgs e)
        {
            BitmapTask();
            Bitmap currentImage = new Bitmap(pictureWindow.Image);
            pictureWindow.Image = ConvolutionMatrix.edge_enhance(currentImage);

        }

        private void embossToolStripMenuItem_Click(object sender, EventArgs e)
        {
            BitmapTask();
            Bitmap currentImage = new Bitmap(pictureWindow.Image);
            pictureWindow.Image = ConvolutionMatrix.emboss(currentImage);

        }

        private void meanRemovalToolStripMenuItem_Click(object sender, EventArgs e)
        {
            BitmapTask();
            Bitmap currentImage = new Bitmap(pictureWindow.Image);
            pictureWindow.Image = ConvolutionMatrix.mean_removal(currentImage);

        }

        private void cropToolStripMenuItem_Click(object sender, EventArgs e)
        {

            if (ToolBox.drawTool.GetType() == new SelectTool().GetType()) //selectTool.isSelected())
            {
                //cropToolStripMenuItem.Enabled = true;
                BitmapTask();
                Bitmap toCrop = new Bitmap(pictureWindow.Image);
                Bitmap croppedImage = toCrop.Clone(ToolBox.drawTool.currentRectangle(), toCrop.PixelFormat);  //Crop the image

                Size tempSize = pictureWindow.Size;                 //Must update the size of the picture window.
                tempSize.Width = croppedImage.Width;
                tempSize.Height = croppedImage.Height;
                pictureWindow.Size = tempSize;

                pictureWindow.Image = croppedImage;              //Sets the picture window to the cropped image.

                ToolBox.drawTool.deselect(pictureWindow, this);
            }
            else
            {
                MessageBox.Show("Use the Select tool to drag and make a rectangle before cropping.", "Error");
                //redoToolStripMenuItem.Enabled = false;
            }
        }

        private void cutToolStripMenuItem_Click(object sender, EventArgs e)
        {
            Type drawType = ToolBox.drawTool.GetType();

            if (drawType == new SelectTool().GetType() ||
                drawType == new ElipseSelectTool().GetType() ||
                drawType == new PolySelectTool().GetType())
            {
                BitmapTask();
                ToolBox.drawTool.cut(pictureWindow); //set the selected area to clear
                //Sets the picture window to the cropped image.
                ToolBox.drawTool.deselect(pictureWindow, this);
            }
            else
            {
                MessageBox.Show("Nothing selected.", "Error");
            }

        }

        private void copyToolStripMenuItem_Click(object sender, EventArgs e)
        {
            Type drawType = ToolBox.drawTool.GetType();

            if (drawType == new SelectTool().GetType() ||
                drawType == new ElipseSelectTool().GetType() ||
                drawType == new PolySelectTool().GetType())
            {
                ToolBox.drawTool.copy(pictureWindow);  //copy image to clipboard
            }
            else
            {
                MessageBox.Show("Select tool not selected.", "Error");
                //redoToolStripMenuItem.Enabled = false;
            }
        }

        private void pasteToolStripMenuItem_Click(object sender, EventArgs e)
        {

            ToolBox.drawTool.paste(Clipboard.GetImage());
            //selectTool.paste(Clipboard.GetImage());
            pictureWindow.Invalidate();
        }

        //a list of messages are available online, declare them here
        //to keep it easy to find
        private const UInt32 WM_MOUSEWHEEL = 0x020A;
        private const UInt32 WM_MOUSEHWHEEL = 0x020E;
        private const UInt32 WM_KEYDOWN = 0x0100;


        private const int MW_SCROLL = 25;

        /*
         * Message processing, returing true will cause the message to be marked as read, no other 
         * interfaces will process it, use this to override any interaction
         * */
        public bool PreFilterMessage(ref Message m)
        {
            //WM_KEYDOWN
            if (m.Msg == WM_KEYDOWN)
            {
                Keys keys = ((Keys)((int)m.WParam.ToInt64()));
                if (ToolBox.drawTool.handleInput(keys, pictureWindow))
                    return true;

            }

            if (m.Msg == WM_MOUSEWHEEL)
            {
                //mousewheel message
                if (Control.ModifierKeys == Keys.Control)
                {
                    //control plus mousewheel
                    int i = m.WParam.ToInt32();
                    if (i > 0)
                        if (panelImage.HorizontalScroll.Value < panelImage.HorizontalScroll.Minimum + MW_SCROLL)
                            panelImage.HorizontalScroll.Value = panelImage.HorizontalScroll.Minimum;
                        else
                            panelImage.HorizontalScroll.Value -= MW_SCROLL;
                    else
                        if (panelImage.HorizontalScroll.Value > panelImage.HorizontalScroll.Maximum - MW_SCROLL)
                            panelImage.HorizontalScroll.Value = panelImage.HorizontalScroll.Maximum;
                        else
                            panelImage.HorizontalScroll.Value += MW_SCROLL;
                    return true;
                }
                else
                {
                    //custom scroll wheel controls
                    int i = m.WParam.ToInt32();
                    if (i > 0)
                        if (panelImage.VerticalScroll.Value < panelImage.VerticalScroll.Minimum + MW_SCROLL)
                            panelImage.VerticalScroll.Value = panelImage.VerticalScroll.Minimum;
                        else
                            panelImage.VerticalScroll.Value -= MW_SCROLL;
                    else
                        if (panelImage.VerticalScroll.Value > panelImage.VerticalScroll.Maximum - MW_SCROLL)
                            panelImage.VerticalScroll.Value = panelImage.VerticalScroll.Maximum;
                        else
                            panelImage.VerticalScroll.Value += MW_SCROLL;
                    return true;
                }
            }
            if (m.Msg == WM_MOUSEHWHEEL)
            {
                //horizontal scroll wheel
                int i = m.WParam.ToInt32();
                if (i < 0)
                    if (panelImage.HorizontalScroll.Value < panelImage.HorizontalScroll.Minimum + MW_SCROLL)
                        panelImage.HorizontalScroll.Value = panelImage.HorizontalScroll.Minimum;
                    else
                        panelImage.HorizontalScroll.Value -= MW_SCROLL;
                else
                    if (panelImage.HorizontalScroll.Value > panelImage.HorizontalScroll.Maximum - MW_SCROLL)
                        panelImage.HorizontalScroll.Value = panelImage.HorizontalScroll.Maximum;
                    else
                        panelImage.HorizontalScroll.Value += MW_SCROLL;
                return true;
            }

            return false;
        }
    }
}
